package com.ezandroid.library.network.http.request;

import android.os.SystemClock;
import android.util.Log;

import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.SocketTimeoutException;
import java.util.Collections;
import java.util.Map;
import java.util.TreeMap;

import okhttp3.Headers;
import okhttp3.Response;
import okio.Buffer;
import okio.GzipSource;

/**
 * A network performing {@link Request} over a {@link HttpStack}.
 *
 * @author markmjw
 * @date 2015-05-29
 */
public class NetworkImpl implements Network {
    private static final String CONTENT_ENCODING = "Content-Encoding";
    private static final String ENCODING_GZIP = "gzip";

    protected final HttpStack mHttpStack;

    public NetworkImpl(HttpStack httpStack) {
        mHttpStack = httpStack;
    }

    @Override
    public NetworkResponse execute(Request<?> request) throws RequestException {
        long requestStart = SystemClock.elapsedRealtime();
        String url = request.getUrl();
        Response httpResponse = null;
        byte[] responseContents = null;
        Map<String, String> responseHeaders = Collections.emptyMap();
        try {
            // execute request
            httpResponse = mHttpStack.performRequest(request);
            int statusCode = httpResponse.code();

            // convertHeaders
            responseHeaders = convertHeaders(httpResponse.headers());

            if (httpResponse.body() != null) {
                if (responseGzip(responseHeaders)) {
                    Buffer buffer = new Buffer();
                    GzipSource gzipSource = new GzipSource(httpResponse.body().source());
                    long length = 0;
                    while (length != -1) {
                        length = gzipSource.read(buffer, Integer.MAX_VALUE);
                    }
                    responseContents = buffer.readByteArray();
                } else {
                    responseContents = httpResponse.body().bytes();
                }
            } else {
                responseContents = new byte[0];
            }

            if (statusCode < HttpURLConnection.HTTP_OK ||
                    statusCode >= HttpURLConnection.HTTP_MULT_CHOICE) {
                throw new IOException();
            }
            return new NetworkResponse(statusCode, responseContents, responseHeaders, false,
                    SystemClock.elapsedRealtime() - requestStart);
        } catch (SocketTimeoutException e) {
            Log.e("NetworkImpl", Log.getStackTraceString(e));
            throw new RequestException(e);

        } catch (MalformedURLException e) {
            Log.e("NetworkImpl", Log.getStackTraceString(e));
            throw new RuntimeException("Bad url " + url, e);

        } catch (IOException e) {
            int statusCode;
            if (httpResponse != null) {
                statusCode = httpResponse.code();
            } else {
                throw new RequestException(e);
            }

            Log.e("NetworkImpl", "Unexpected response code " + statusCode + " for " + url);
            Log.e("NetworkImpl", Log.getStackTraceString(e));

            if (responseContents != null) {
                NetworkResponse networkResponse = new NetworkResponse(statusCode,
                        responseContents, responseHeaders, false);
                throw new RequestException(networkResponse);
            } else {
                throw new RequestException(e);
            }
        }
    }

    private boolean responseGzip(Map<String, String> headers) {
        for (Map.Entry<String, String> entry : headers.entrySet()) {
            if (entry.getKey().equalsIgnoreCase(CONTENT_ENCODING)
                    && entry.getValue().equalsIgnoreCase(ENCODING_GZIP)) {
                return true;
            }
        }
        return false;
    }

    protected static Map<String, String> convertHeaders(Headers headers) {
        Map<String, String> result = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
        if (null != headers) {
            for (String field : headers.names()) {
                result.put(field, headers.get(field));
            }
        }
        return result;
    }
}
